Skip to content

Cosmos: Adds Binary Encoding Support for Queries and Streaming order_by - #5040

Draft
Debdatta Kunda (kundadebdatta) wants to merge 16 commits into
mainfrom
users/kundadebdatta/4305_support_binary_encoding_for_query
Draft

Cosmos: Adds Binary Encoding Support for Queries and Streaming order_by#5040
Debdatta Kunda (kundadebdatta) wants to merge 16 commits into
mainfrom
users/kundadebdatta/4305_support_binary_encoding_for_query

Conversation

@kundadebdatta

@kundadebdatta Debdatta Kunda (kundadebdatta) commented Aug 11, 2026

Copy link
Copy Markdown
Member

Extends Cosmos binary JSON (the 0x80-preamble wire format) from point operations to the query path. Previously a query_items call always received text pages, even with binary encoding enabled — which meant queries kept the integral-Double → integer deserialization divergence (#5028) that binary encoding exists to fix.

Opt-in and off by default; with the flag unset, behavior is byte-for-byte unchanged.

What changed

Negotiation. Queries now advertise x-ms-cosmos-supported-serialization-formats: CosmosBinary, set once at the plan_operation choke point that every per-page request flows through. An explicitly caller-set header is never clobbered, and request_text_response still forces text. The query request body stays text by design — application/query+json is a query spec, not a document.

All three query pipelines handle binary pages:

Pipeline Change
Passthrough (single + cross-partition) binary flows through into_single
Streaming ORDER BY parse_envelope_page decodes binary envelopes; merged items emitted as binary
OFFSET / LIMIT / TOP split_feed_envelope splits binary pages into per-document binary

OFFSET/LIMIT/TOP was a blocker: cross-partition skip/take failed outright on binary pages before this.

Emitting merged ORDER BY items as binary is semantically load-bearing, not cosmetic. The binary deserializer coerces a service-echoed integral Double into an integer target; the text deserializer hard-fails on it. Emitting text would reintroduce the exact divergence for ORDER BY that passthrough queries no longer have.

How a binary query page flows

sequenceDiagram
    participant SDK
    participant D as Driver
    participant S as Service

    SDK->>D: query_items (binary enabled)
    D->>S: page request + CosmosBinary header
    S-->>D: binary page (0x80 preamble)

    alt ORDER BY
        D->>D: decode envelope, merge on sort keys
        D->>D: re-encode merged items to binary
    else OFFSET / LIMIT / TOP
        D->>D: split envelope, re-encode each document standalone
    else passthrough
        D->>D: forward page unchanged
    end

    D-->>SDK: binary items
    SDK->>SDK: decode (auto-detect by preamble)
Loading

Every producer emits each document standalone-encoded, so into_items auto-detects format per item by preamble.

Testing

  • End-to-end round-trip incl. cross-partition skip/take — verified load-bearing by mutation (disabling the binary branch fails with the expected envelope-parse error)
  • Fuzzer extended to cover the query path, ORDER BY included
  • Corpus validation now runs passthrough and ORDER BY queries per sampled document
  • Byte-level assertions that a binary query yields a 0x80 body and a text query does not
  • Negative coverage: binary-disabled queries advertise no format

Queries now advertise a binary response via x-ms-cosmos-supported-serialization-formats while keeping their application/query+json request body as text. Splits the driver binary gate into request-body encoding (point item ops) and response negotiation (item ops + query), sets the header at the plan_operation choke point every query page flows through, wires binary resolution into query_items, and honors the negotiation in the in-memory emulator feed responses. Adds a driver unit test and an emulator end-to-end binary query round-trip test, and updates the binary-encoding SPEC/HLD docs.
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
2 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@github-actions github-actions Bot added the Cosmos The azure_cosmos crate label Aug 11, 2026
Adds an in-memory-emulator test that runs a full-container SELECT * over a 3-partition container with binary encoding enabled, proving the passthrough cross-partition query path round-trips binary Documents envelopes per page with no additional code beyond response negotiation.
kundadebdatta added 13 commits August 10, 2026 18:59
Records a per-operation comparison of Cosmos binary JSON support (request encode / response negotiate / response decode) between the Rust SDK+driver and azure-cosmos-dotnet-v3, including the ORDER BY/aggregate query-engine gap, the Delete negotiation divergence, the patch mechanism difference, and the header-value nuance, with source references on both sides.
Make the streaming ORDER BY merge binary-aware: parse_envelope_page now transcodes a binary-negotiated page to text before the envelope parse (no-op for text). Adds unit + integration coverage, extends the e2e round-trip fuzzer with single-partition and cross-partition ORDER BY query round-trips, and updates the .NET parity doc.
… gaps

Addresses re-review findings #3 (correctness), #8, #9, #10.

#3 — binary ORDER BY lost the native integral-Double->integer coercion that
passthrough binary queries get, so a typed model with a wide integer field
could round-trip through a passthrough query but fail on an ORDER BY query
for the same document (the text/binary divergence, #5028). The streaming
merge transcoded each binary page to text and rebuilt a *text* envelope, so
the SDK decoded it with the text deserializer (which hard-fails on a
service-echoed integral double for an integer field).
  - PageAggregator now tracks whether the source pages were binary and, when
    so, re-encodes the assembled envelope to Cosmos binary JSON in build_page,
    so the SDK's binary deserializer runs its integral-Double->integer
    coercion — matching passthrough. Text sources keep the zero-copy text
    envelope.
  - New unit tests prove the emitted format follows the source and that an
    integral-Double u64 payload now decodes (and would fail as text).
  - ids_in_page test helper is now format-aware (transcodes binary pages).
  - Live fuzzer: the typed IntProbe now also decodes through a cross-partition
    binary ORDER BY, exercising the merge coercion end to end.

#8 — binary_cross_partition_query_round_trips could pass even if negotiation
silently broke (text decodes fine). build_multi_partition_container now
attaches a QueryRequestRecorder and the test asserts every fan-out page
advertised a binary response with a text body.

#9 — added binary_cross_partition_order_by_merges_and_round_trips: an
always-run emulator test that exercises the real k-way merge over binary
pages (previously only a mocked driver test + the live-only fuzzer covered it).

#10 — the fuzzer's cross-partition ORDER BY fan-out (the most expensive query
shape) now runs only on the binary configs, where it adds coverage; the
single-partition passthrough query still runs on every config.
A - BINARY_NEGOTIATION_FORMATS doc comment was written in #4671 and never
updated when this PR wired query negotiation. It claimed the constant applied
'on point operations' and that query negotiation was 'not yet wired' - the
exact thing this PR ships. Rewrote it to cover point ops + query and to state
explicitly why Rust forces CosmosBinary for query (vs .NET's
JsonText,CosmosBinary), matching the SPEC.

B - BINARY_ENCODING_SPEC.md listed 'delete' in the request-body gate in two
places, but the code (supports_binary_request_body) and its unit test exclude
delete. Dropped delete from both lists and noted the .NET divergence
(.NET's IsPointOperationSupportedForBinaryEncoding does include delete).

C - the rewritten query unit test only re-asserted two booleans already
covered elsewhere and lost the behavioral guarantee its predecessor had.
Replaced it with a behavioral test that drives a real query operation through
apply_response_negotiation (the actual header owner) and asserts the
application/query+json body stays text while the response advertises binary.
#7 - avoid resolving the binary-encoding options view twice per point op.
execute_operation already resolves BinaryEncodingOptions for the request-body
gate, but plan_operation -> apply_response_negotiation re-resolved the same
layered view. Thread the resolved value through a private
plan_operation_resolved into apply_response_negotiation; the public
plan_operation (and the query path, which reaches the driver there directly)
passes None and resolves lazily as before. Also corrected the now-stale
comment claiming execute_operation sets the negotiation header (it no longer
does after #6 - apply_response_negotiation owns it).

#12 - replace the bare positional 'binary: bool' on success_feed_response /
success_document_feed_response with a ResponseFormat { Text, Binary } enum, so
the four read-feed/change-feed call sites read ResponseFormat::Text instead of
a naked 'false' that a future edit could transpose (restores #4733's
positional-creep guard). Query call sites use ResponseFormat::from(
parsed.binary_response). Also documented the emulator's binary-response
fidelity note in dispatch.rs (derives the flag from the header alone vs the
real gateway honoring it only for Query - unreachable since Rust only
advertises binary for point ops + query).
- #1 (blocking): make the binary-emit flag sticky on StreamingOrderedMerge so
  buffer-only pages (no backend fetch) still emit binary; a per-page flag left
  them text with float-widened integers that failed typed decode. + regression test.
- #2: scope the BINARY_NEGOTIATION_FORMATS doc comment to note request_text_response
  is honored only for point ops, not queries.
- #4: correct the stale into_items splitter comment (real reason it stays inert).
- #11: assert query-plan requests carry no binary header instead of skipping them.
- #14: move ResponseFormat below success_response_with_format to fix its orphaned doc.
- #8: add CHANGELOG entries (SDK + driver) for query binary negotiation.
…ding_for_query

# Conflicts:
#	sdk/cosmos/azure_data_cosmos_driver/src/driver/cosmos_driver.rs
#	sdk/cosmos/azure_data_cosmos_driver/src/driver/dataflow/integration_tests/order_by_resume.rs
#	sdk/cosmos/azure_data_cosmos_driver/src/driver/dataflow/query_response.rs
assert_query_roundtrip gained an 8th parameter (the ORDER-BY gate) that trips
clippy::too_many_arguments under -Dwarnings. Bundling the args would only move
the count into assert_query_hit/assert_roundtrip, so allow it on this test-only
helper.
Closes the remaining review findings on binary response negotiation for
queries, and covers two gaps the review surfaced.

Correctness:
- split_feed_envelope now handles Cosmos binary pages. OFFSET/LIMIT and TOP
  route through the SkipTake node, whose splitter was text-only, so those
  queries hard-failed with binary enabled.
- A query with request_text_response no longer negotiates binary. Queries
  bypass the execute_operation transcode, so negotiating would hand a
  text-requesting caller binary pages.
- Response negotiation no longer clobbers a caller-set format header.
- emit_binary is promoted on the merge fill-error path and OR-assigned at the
  page bottom, so a sticky binary flag cannot be cleared.
- build_page transcode failures are classified as
  SERIALIZATION_RESPONSE_BODY_INVALID (client-side re-encode) rather than a
  500 service error, and carry the item ordinal.

Tests:
- End-to-end binary OFFSET/LIMIT and TOP round-trip against the emulator,
  verified load-bearing by mutation.
- Byte-level query response assertions for both CosmosBinary and JsonText.
- A binary-disabled query advertises no serialization format.
- The sampled-corpus test now issues passthrough and ORDER BY queries per
  document; it previously only exercised create and read, so it was not
  validating query binary support at all.
- Removed two vacuous assertions (empty-page format checks, unverified
  request bodies).

Docs:
- The Gateway 2.0 / thin-client text fallback is documented as a
  customer-visible limitation in both the SPEC and the HLD; the repeated
  per-document transcode is recorded as deferred work.
- Added a comment-brevity convention to AGENTS.md and trimmed the verbose
  comment blocks this PR had accumulated.
Replace the "Deferred work" prose section with "Binary encoding support
status": a per-operation table (request encode / response negotiate /
response decode) and a numbered pending-work table with severity and size.

Corrections:

- The deferred-work note recommended slicing a document out of a binary
  page and re-prefixing it with 0x80. That is unsound: reference strings
  (STR_R1-STR_R4) resolve against absolute page offsets and the interning
  scope is the whole page, so a detached sub-slice mis-resolves any
  reference pointing outside it, silently returning wrong text. Replaced
  with a view-based design (refcounted page Bytes plus an offset).
- The "binary feed responses" bullet claimed the feed splitter is text-only
  and cannot handle binary envelopes. split_feed_envelope handles them as
  of this branch. Rewritten as the invariant future splitters must keep.
- Aggregate / GROUP BY / DISTINCT were listed as a binary gap. They are
  rejected cross-partition in any encoding, so they are blocked on the
  query engine rather than pending binary work.

Restore the Rust vs .NET parity matrix (dropped in e7af8fda59) as a section
here rather than a separate internal doc, updated for this branch: TOP /
LIMIT / OFFSET now ship, and the Gateway 2.0 row carries the customer-visible
framing instead of "still decodes".
@kundadebdatta Debdatta Kunda (kundadebdatta) changed the title Cosmos: Negotiate binary response for queries Cosmos: Adds Binary Encoding Support for Queries and Streaming order_by Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

binary-encoding Cosmos The azure_cosmos crate

Projects

Status: Triage

Development

Successfully merging this pull request may close these issues.

1 participant